package com.seu.githubcn.config;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.seu.githubcn.entity.Manager;
import com.seu.githubcn.entity.Repo;
import com.seu.githubcn.entity.SshFingerprint;
import com.seu.githubcn.entity.User;
import com.seu.githubcn.mapper.ManagerMapper;
import com.seu.githubcn.mapper.RepoMapper;
import com.seu.githubcn.mapper.SshFingerprintMapper;
import com.seu.githubcn.mapper.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.sshd.common.AttributeRepository;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.git.pack.GitPackCommandFactory;
import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

@Configuration
@PropertySource("classpath:/application.yml")
@Slf4j
public class SshServerConfig {
    static AttributeRepository.AttributeKey<Integer> userIdKey =
            new AttributeRepository.AttributeKey<>();
    @Value("${githubcn.ssh.server-host:localhost}")
    String serverHost;
    @Value("${githubcn.ssh.server-port:22}")
    Integer serverPort;

    @Value("${githubcn.base-path:/null}")
    String basePath;

    @Value("${githubcn.ssh.host-key-file:/etc/ssh/ssh_host_rsa_key}")
    String sshHostKeyFile;

    @Autowired
    SshFingerprintMapper fingerprintMapper;
    @Autowired
    UserMapper userMapper;
    @Autowired
    RepoMapper repoMapper;
    @Autowired
    ManagerMapper managerMapper;

    private void setupSshServer(SshServer server) {
        server.setHost(serverHost);
        server.setPort(serverPort);

        server.setPublickeyAuthenticator((username, key, session) -> {
            log.info("Key fingerprint: " + KeyUtils.getFingerPrint(key));
            if (!"git".equals(username)) {
                return false;
            }

            SshFingerprint result = fingerprintMapper.selectOne(Wrappers
                    .<SshFingerprint>lambdaQuery()
                    .eq(SshFingerprint::getFingerprint, KeyUtils.getFingerPrint(key)));

            if (result != null && userMapper.exists(Wrappers
                    .<User>lambdaQuery()
                    .ne(User::getUserPermission, User.Permission.BANNED)
            )) {
                session.setAttribute(userIdKey, result.getOwnerId());
                return true;
            } else {
                return false;
            }
        });

        server.setCommandFactory(new GitPackCommandFactory()
                .withGitLocationResolver((command, args, session, fs) -> {
                    final String[] argSlidesArr = args[1].split("/");
                    final Integer userId = session.getAttribute(userIdKey);
                    final Path resultPath = Path.of(basePath.substring(0, basePath.indexOf("/" + "%s")));

                    List<String> argSlides;
                    // 格式不对
                    if (argSlidesArr.length == 2) {
                        argSlides = List.of(argSlidesArr);
                    } else if (argSlidesArr.length == 3 && argSlidesArr[0].equals("")) {
                        argSlides = Arrays.stream(argSlidesArr).skip(1).toList();
                    } else {
                        log.warn("Incorrect repo format: " + args[1]);
                        return null;
                    }

                    User owner = userMapper.selectOne(Wrappers
                            .<User>lambdaQuery()
                            .eq(User::getUserName, argSlides.get(0)));
                    // 用户不存在
                    if (owner == null) {
                        log.warn("Non-exist repo: invalid owner name " + argSlides.get(0));
                        return null;
                    }

                    Repo repo = repoMapper.selectOne(Wrappers.<Repo>lambdaQuery()
                            .eq(Repo::getRepoName, argSlides.get(1).replaceAll("\\.git$", ""))
                            .and(i -> i.eq(Repo::getOwnerId, owner.getUserId())));
                    // 仓库不存在
                    if (repo == null) {
                        log.warn("Non-exist repo: incorrect repo name " + argSlides.get(1));
                        return null;
                    }

                    boolean canWrite = Objects.equals(userId, owner.getUserId()) || managerMapper.exists(Wrappers
                            .<Manager>lambdaQuery()
                            .eq(Manager::getRepoId, repo.getRepoId())
                            .and(j -> j.eq(Manager::getManagerId, userId)));

                    if (repo.getIsPrivate() || args[0].equals("git-receive-pack")) {
                        if (!canWrite) {
                            log.warn("No write permission! ");
                        }
                        return canWrite ? resultPath : null;
                    } else {
                        return resultPath;
                    }
                }));

        server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(Path.of(sshHostKeyFile)));
//        server.setSessionFactory(server.getSessionFactory());
    }

    @Bean
    public SshServer createSshServer() throws IOException {
        log.info("Starting SSH Server...");
        SshServer server = SshServer.setUpDefaultServer();
        setupSshServer(server);
        server.open();
        log.info(server.toString());
        return server;
    }
}
